package blob

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
)

type SpdkBsDev string

const MaxNumBlob = 128

type SuperBlock struct {
	SuperBlob  int64
	BlobExists [MaxNumBlob]int8
}

type SpdkBlobStore struct {
	Dev       SpdkBsDev
	SuperFile *os.File
	SuperBlk  *SuperBlock
	Blobs     [MaxNumBlob]*SpdkBlob
}

type SpdkBlob struct {
	filp      *os.File
	id        SpdkBlobId
	bs        *SpdkBlobStore
}

type SpdkBlobId int64

/**
 * Blobstore operation completion callback.
 *
 * \param cb_arg Callback argument.
 * \param bserrno 0 if it completed successfully, or negative errno if it failed.
 */
//typedef void (*spdk_bs_op_complete)(void *cb_arg, int bserrno);
type SpdkBsOpComplete func(cbArg interface{}, bsErrno int)

/**
 * Blobstore operation completion callback with handle.
 *
 * \param cb_arg Callback argument.
 * \param bs Handle to a blobstore.
 * \param bserrno 0 if it completed successfully, or negative errno if it failed.
 */
//typedef void (*spdk_bs_op_with_handle_complete)(void *cb_arg, struct spdk_blob_store *bs, int bserrno);
type SpdkBsOpWithHandleComplete func(cbArg interface{}, bs *SpdkBlobStore, bsErrno int)

/**
 * Blob operation completion callback.
 *
 * \param cb_arg Callback argument.
 * \param bserrno 0 if it completed successfully, or negative errno if it failed.
 */
//typedef void (*spdk_blob_op_complete)(void *cb_arg, int bserrno);
type SpdkBlobOpComplete func(cbArg interface{}, bsErrno int)

/**
 * Blob operation completion callback with blob ID.
 *
 * \param cb_arg Callback argument.
 * \param blobid Blob ID.
 * \param bserrno 0 if it completed successfully, or negative errno if it failed.
 */
//typedef void (*spdk_blob_op_with_id_complete)(void *cb_arg, spdk_blob_id blobid, int bserrno);
type SpdkBlobOpWithIdComplete func(cbArg interface{}, blobid SpdkBlobId, bsErrno int)

/**
 * Blob operation completion callback with handle.
 *
 * \param cb_arg Callback argument.
 * \param bs Handle to a blob.
 * \param bserrno 0 if it completed successfully, or negative errno if it failed.
 */
//typedef void (*spdk_blob_op_with_handle_complete)(void *cb_arg, struct spdk_blob *blb, int bserrno);
type SpdkBlobOpWithHandleComplete func(cbArg interface{}, blb *SpdkBlob, bsErrno int)

type SpdkBsOpts struct {
	/** Size of cluster in bytes. Must be multiple of 4KiB page size. */
	//uint32_t cluster_sz;
	ClusterSz uint32

	/** Count of the number of pages reserved for metadata */
	//uint32_t num_md_pages;
	NumMdPages uint32

	/** Maximum simultaneous metadata operations */
	//uint32_t max_md_ops;
	MaxMdOps uint32

	/** Maximum simultaneous operations per channel */
	//uint32_t max_channel_ops;
	MaxChannelOps uint32

	/** Clear method */
	//enum bs_clear_method  clear_method;
	BsClearMethod int

	/** Blobstore type */
	//struct spdk_bs_type bstype;
	BsType int

	/** Callback function to invoke for each blob. */
	//spdk_blob_op_with_handle_complete iter_cb_fn;
	IterCbFn SpdkBlobOpWithHandleComplete

	/** Argument passed to iter_cb_fn for each blob. */
	//void *iter_cb_arg;
	IterCbArg interface{}
}

/**
 * Initialize a spdk_bs_opts structure to the default blobstore option values.
 *
 * \param opts The spdk_bs_opts structure to be initialized.
 */
//void spdk_bs_opts_init(struct spdk_bs_opts *opts);
func SpdkBsOptsInit(opts *SpdkBsOpts) {

}

/**
 * Load a blobstore from the given device.
 *
 * \param dev Blobstore block device.
 * \param opts The structure which contains the option values for the blobstore.
 * \param cb_fn Called when the loading is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_load(struct spdk_bs_dev *dev, struct spdk_bs_opts *opts,
//		  spdk_bs_op_with_handle_complete cb_fn, void *cb_arg);
//func SpdkBsLoad(dev SpdkBsDev, opts *SpdkBsOpts, cbFn SpdkBsOpWithHandleComplete, cbArg interface{}) {
func SpdkBsLoad(dev SpdkBsDev, opts *SpdkBsOpts) *SpdkBlobStore {
	r := new(SpdkBlobStore)
	r.Dev = dev
	superFileName := fmt.Sprintf("%s/super.dat", dev)
	superFile, err := os.OpenFile(superFileName, os.O_RDWR, 0666)
	if err != nil {
		return nil
	}
	sb := new(SuperBlock)
	r.SuperBlk = sb
	r.SuperFile = superFile
	size := binary.Size(*sb)
	buf := make([]byte, size)
	if n, err := superFile.Read(buf); err != nil || n != size {
		log.Printf("superFile.Read(), err=%v, n=%d, size=%d\n", err, n, size)
		return nil
	}
	rd := bytes.NewReader(buf)
	if err := binary.Read(rd, binary.LittleEndian, sb); err != nil {
		log.Println("binary.Read failed:", err)
		return nil
	}

	return r
}

/**
 * Initialize a blobstore on the given device.
 *
 * \param dev Blobstore block device.
 * \param opts The structure which contains the option values for the blobstore.
 * \param cb_fn Called when the initialization is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_init(struct spdk_bs_dev *dev, struct spdk_bs_opts *opts,
//		  spdk_bs_op_with_handle_complete cb_fn, void *cb_arg);
//func SpdkBsInit(dev SpdkBsDev, opts *SpdkBsOpts, cbFn SpdkBsOpWithHandleComplete, cbArg interface{}) {
func SpdkBsInit(dev SpdkBsDev, opts *SpdkBsOpts) *SpdkBlobStore {
	superFileName := fmt.Sprintf("%s/super.dat", dev)
	if _, err := os.Stat(superFileName); err == nil {
		log.Printf("Super file already exists\n")
		return nil
	}

	superFile, err := os.OpenFile(superFileName, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil
	}

	r := new(SpdkBlobStore)
	r.Dev = dev
	r.SuperFile = superFile
	sb := new(SuperBlock)
	sb.SuperBlob = -1
	r.SuperBlk = sb
	SpdkBsMdSync(r)
	return r
}

func SpdkBsExists(dev SpdkBsDev) bool {
	superFileName := fmt.Sprintf("%s/super.dat", dev)
	if _, err := os.Stat(superFileName); err == nil {
		return true
	}
	return false
}

func SpdkBsMdSync(bs *SpdkBlobStore) bool {
	buf := new(bytes.Buffer)
	if err := binary.Write(buf, binary.LittleEndian, bs.SuperBlk); err != nil {
		log.Printf("Write, err=%v\n", err)
		return false
	}

	size := binary.Size(*bs.SuperBlk)
	n, err := bs.SuperFile.WriteAt(buf.Bytes(), 0)
	if err != nil || n != size {
		log.Printf("SpdkBsMdSync: WriteAt(), err=%v\n", err)
		return false
	}
	err = bs.SuperFile.Sync()
	if err != nil {
		log.Printf("SpdkBsMdSync: Sync(), err=%v\n", err)
		return false
	}
	return true
}

/**
 * Destroy the blobstore.
 *
 * It will destroy the blobstore by zeroing the super block.
 *
 * \param bs blobstore to destroy.
 * \param cb_fn Called when the destruction is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_destroy(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn, void *cb_arg);
//func SpdkBsDestroy(bs *SpdkBlobStore, cbFn SpdkBsOpComplete, cbArg interface{}) {
func SpdkBsDestroy(bs *SpdkBlobStore) bool {
	superFileName := fmt.Sprintf("%s/super.dat", bs.Dev)
	bs.SuperFile.Close()
	err := os.Remove(superFileName)
	if err != nil {
		log.Printf("os.Remove(): err=%v\n", err)
		return false
	}
	bs.SuperFile = nil
	bs.SuperBlk = nil
	return true
}

/**
 * Unload the blobstore.
 *
 * It will flush all volatile data to disk.
 *
 * \param bs blobstore to unload.
 * \param cb_fn Called when the unloading is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_unload(struct spdk_blob_store *bs, spdk_bs_op_complete cb_fn, void *cb_arg);
//func SpdkBsUnload(bs *SpdkBlobStore, cbFn SpdkBsOpComplete, cbArg interface{}) {
func SpdkBsUnload(bs *SpdkBlobStore) {
	SpdkBsMdSync(bs)
	bs.SuperFile.Close()
	bs.SuperFile = nil
	bs.SuperBlk = nil
}

/**
 * Set a super blob on the given blobstore.
 *
 * This will be retrievable immediately after spdk_bs_load() on the next initializaiton.
 *
 * \param bs blobstore.
 * \param blobid The id of the blob which will be set as the super blob.
 * \param cb_fn Called when the setting is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_set_super(struct spdk_blob_store *bs, spdk_blob_id blobid,
//		       spdk_bs_op_complete cb_fn, void *cb_arg);
//func SpdkBsSetSuper(bs *SpdkBlobStore, blobid SpdkBlobId, cbFn SpdkBsOpComplete, cbArg interface{}) {
func SpdkBsSetSuper(bs *SpdkBlobStore, blobid SpdkBlobId) bool {
	if bs.SuperFile == nil || bs.SuperBlk == nil {
		return false
	}
	bs.SuperBlk.SuperBlob = int64(blobid)
	SpdkBsMdSync(bs)
	return true
}

/**
 * Get the super blob. The obtained blob id will be passed to the callback function.
 *
 * \param bs blobstore.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_get_super(struct spdk_blob_store *bs,
//		       spdk_blob_op_with_id_complete cb_fn, void *cb_arg);
//func SpdkBsGetSuper(bs *SpdkBlobStore, blobid SpdkBlobId, cbFn SpdkBsOpComplete, cbArg interface{}) {
func SpdkBsGetSuper(bs *SpdkBlobStore) SpdkBlobId {
	return SpdkBlobId(bs.SuperBlk.SuperBlob)
}

/**
 * Create a new blob with default option values on the given blobstore.
 * The new blob id will be passed to the callback function.
 *
 * \param bs blobstore.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_create_blob(struct spdk_blob_store *bs, spdk_blob_op_with_id_complete cb_fn, void *cb_arg);
func SpdkBsCreateBlob(bs *SpdkBlobStore) SpdkBlobId {
	id := -1
	for i := 0; i < MaxNumBlob; i++ {
		if bs.SuperBlk.BlobExists[i] == 0 {
			id = i
			break
		}
	}
	if id < 0 {
		return -1
	}
	fileName := fmt.Sprintf("%s/blob%03d.dat", bs.Dev, id)
	filp, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		log.Printf("SpdkBsCreateBlob(): os.Create, err=%s\n", err)
		return -1
	}
	filp.Close()
	bs.SuperBlk.BlobExists[id] = 1
	SpdkBsMdSync(bs)
	return SpdkBlobId(id)
}

/**
 * Delete an existing blob from the given blobstore.
 *
 * \param bs blobstore.
 * \param blobid The id of the blob to delete.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_delete_blob(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_op_complete cb_fn, void *cb_arg);
func SpdkBsDeleteBlob(bs *SpdkBlobStore, blobId SpdkBlobId) bool {
	if bs.SuperBlk.BlobExists[blobId] == 0 {
		log.Printf("the blobId(%d) doesn't exist\n", blobId)
		return false
	}
	if bs.Blobs[blobId] != nil {
		log.Printf("You must close a blob before delete it\n")
		return false
	}
	bs.SuperBlk.BlobExists[blobId] = 0
	SpdkBsMdSync(bs)
	fileName := fmt.Sprintf("%s/blob%03d.dat", bs.Dev, blobId)
	if _, err := os.Stat(fileName); err != nil {
		return true
	}
	err := os.Remove(fileName)
	if err != nil {
		log.Printf("Remove() , err = %s\n", err)
		return false
	}
	return true
}

/**
 * Open a blob from the given blobstore.
 *
 * \param bs blobstore.
 * \param blobid The id of the blob to open.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_bs_open_blob(struct spdk_blob_store *bs, spdk_blob_id blobid, spdk_blob_op_with_handle_complete cb_fn, void *cb_arg);
func SpdkBsOpenBlob(bs *SpdkBlobStore, blobId SpdkBlobId) *SpdkBlob {
	if bs.SuperBlk.BlobExists[blobId] == 0 {
		log.Printf("SpdkBsOpenBlob(): blobId=%d doesn't exist\n", int64(blobId))
		return nil
	}
	if bs.Blobs[blobId] != nil {
		return bs.Blobs[blobId]
	}
	fileName := fmt.Sprintf("%s/blob%03d.dat", bs.Dev, blobId)
	filp, err := os.OpenFile(fileName, os.O_RDWR, 0666)
	if err != nil {
		log.Printf("SpdkBsOpenBlob(): cannot open file, err=%s\n", err)
		return nil
	}
	ret := new(SpdkBlob)
	ret.filp = filp
	ret.id = blobId
	ret.bs = bs
	bs.Blobs[blobId] = ret

	return ret
}

/**
 * Resize a blob to 'sz' clusters. These changes are not persisted to disk until
 * spdk_bs_md_sync_blob() is called.
 * If called before previous resize finish, it will fail with errno -EBUSY
 *
 * \param blob Blob to resize.
 * \param sz The new number of clusters.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 *
 */
//void spdk_blob_resize(struct spdk_blob *blob, uint64_t sz, spdk_blob_op_complete cb_fn, void *cb_arg);
func SpdkBlobResize(blob *SpdkBlob, sz uint64) bool {
	log.Printf("This fucntion(SpdkBlobResize) is under construction\n")
	return false
}

/**
 * Sync a blob.
 *
 * Make a blob persistent. This applies to open, resize, set xattr, and remove
 * xattr. These operations will not be persistent until the blob has been synced.
 *
 * \param blob Blob to sync.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_blob_sync_md(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg);
func SpdkBlobSyncMd(blob *SpdkBlob) bool {
	blob.filp.Sync()
	return true
}

/**
 * Close a blob. This will automatically sync.
 *
 * \param blob Blob to close.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_blob_close(struct spdk_blob *blob, spdk_blob_op_complete cb_fn, void *cb_arg);
func SpdkBlobClose(blob *SpdkBlob) bool {
	blob.filp.Close()
	blob.bs.Blobs[blob.id] = nil
	return true
}

/**
 * Write data to a blob.
 *
 * \param blob Blob to write.
 * \param channel The I/O channel used to submit requests.
 * \param payload The specified buffer which should contain the data to be written.
 * \param offset Offset is in io units from the beginning of the blob.
 * \param length Size of data in io units.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_blob_io_write(struct spdk_blob *blob, struct spdk_io_channel *channel,
//			void *payload, uint64_t offset, uint64_t length,
//			spdk_blob_op_complete cb_fn, void *cb_arg);

func SpdkBlobIoWrite(blob *SpdkBlob, payload []byte, offset int64) bool {
	n, err := blob.filp.WriteAt(payload, offset)
	if err != nil {
		fmt.Printf("SpdkBlobIoWrite(): WriteAt, err = %s\n", err)
		return false
	}
	if n != len(payload) {
		fmt.Printf("SpdkBlobIoWrite(): WriteAt, n=%d, len(payload)=%d\n", n, len(payload))
		return false
	}
	return true
}

/**
 * Read data from a blob.
 *
 * \param blob Blob to read.
 * \param channel The I/O channel used to submit requests.
 * \param payload The specified buffer which will store the obtained data.
 * \param offset Offset is in io units from the beginning of the blob.
 * \param length Size of data in io units.
 * \param cb_fn Called when the operation is complete.
 * \param cb_arg Argument passed to function cb_fn.
 */
//void spdk_blob_io_read(struct spdk_blob *blob, struct spdk_io_channel *channel,
//		       void *payload, uint64_t offset, uint64_t length,
//		       spdk_blob_op_complete cb_fn, void *cb_arg);
func SpdkBlobIoRead(blob *SpdkBlob, offset int64, length int) ([]byte, bool) {
	buf := make([]byte, length)
	n, err := blob.filp.ReadAt(buf, offset)
	if err != nil {
		log.Printf("SpdkBlobIoRead(): ReadAt, err = %s\n", err)
		return []byte(""), false
	}
	if n != length {
		log.Printf("SpdkBlobIoRead(): ReadAt, n=%d, length=%d\n", n, length)
		return []byte(""), false
	}
	return buf, true
}

func SpdkBsGetIoUnitSize(bs *SpdkBlobStore) uint64 {
	return 512
}

func init() {
	log.Printf("blob starting ...\n")
}

